Cookie 和 Session 這兩個名詞,相信大部分的開發者都不會太陌生,特別是 Cookie,從社群網站、電商平台、Google Analytics 分析等地方,無處不見它的應用;但從本質上,Cookie & Session 究竟是什麼呢?這就要從 HTTP 的特性說起。
本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!
小明喜歡帶著 MAC 去星巴克當潮潮,每周總是會擠出時間去星巴克喝咖啡用電腦,常常一坐就是一整天;從最一開始不知道要點什麼,逐漸喝到辦了隨行卡,還成為金星級會員;而正妹店員也從詢問「先生貴姓」,轉變成讀取隨行卡後詢問「小明您的隨行卡餘額還有 280 元喔」。
注意到關鍵了嗎?對就是 正妹店員 隨行卡。在沒有隨行卡的情況下,店員不知道小明是誰,對店員來說,每一位客人都是單次的事件,,必須要透過隨行卡,才能知道小明是誰、剩下多少餘額,也就是小明在星巴克的「狀態」。
昨天 我們討論了 RESTful API 時,提到了 HTTP 是一個無狀態的通訊協定,就如同星巴克的店員不會記住客人是誰一樣,伺服器不會記住使用者是誰,而是把每一次收到的請求都視為獨立的行為。
如果是純粹展示靜態內容的網站,無狀態不會是個問題,反而是很棒的優點,因為客戶端、伺服器、資料庫都不需要儲存使用者狀態,也就省去了大量的儲存空間;但在複雜的網路應用,例如需要辨別使用者身分時,無狀態的通訊就會是個問題。
那如果要讓無狀態的 HTTP 能記住使用者是誰,該怎麼做呢?
還記得剛剛的隨行卡嗎?如果店家對每個使用者發出一個會員卡,就能夠透過會員卡來辨別使用者是誰了;同樣的道理,在 HTTP 的規範 - 狀態管理機制 的章節中,規範了一套讓無狀態的 HTTP 能得知使用者狀態的方法;簡單來說,就是伺服器透過 Header 的屬性 Set-Cookie
,把使用者的狀態紀錄成儲存在使用者電腦裡的 Cookie,而瀏覽器在每一次發送請求時,都在 Header 中設定 Cookie
屬性,把 Cookie 帶上,伺服器就能藉由檢視 Cookie 的內容,得知瀏覽器使用者的狀態;而像是「從登入到登出」、「從開始瀏覽網頁到 Cookie 失效」,或是任何伺服器能認出使用者狀態的時間區間,就叫做 Session。
例如瀏覽器回應的內容如下:
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
而瀏覽器就會依照 Set-Cookie
的內容,建立、儲存指定的鍵值對(key-value pair);當瀏覽器要發送請求時,就會將 Cookies 帶上:
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
另外,前端開發者可以透過 document.cookie
,在 JavaScript 中取得當前的 Cookies:
console.log(document.cookie)
// yummy_cookie=choco; tasty_cookie=strawberry
關於更多規範提到的詳細內容,推薦可以參考 Huli 的淺談 Session 與 Cookie:一起來讀 RFC。
Cookie 除了單純的鍵值對之外,伺服器也可以在 Set-Cookie
內標註這組資料的額外屬性:
domain
:Cookie 的有效 domain,如果未設定,就會自動綁在執行 Set-Cookie 的 domain 下;雖然可以自行設置,但其實也只能在一級/次級網域之間調整,寫到其他人的 domain 是寫不進去的。path
:可以指定 Cookie 只在特定路徑下生效,未設定預設為 '/'
Max-Age
:有效期限,單位是秒;當數值為正數時有效,負數時為本次 Session 有效;0 為刪除 CookieExpires
:同上,只是指定的是時間點secure
:安全,當這個屬性被設為 true 時,此 Cookie 就只會在「安全的協議」下傳輸,通常為 HTTPSHttpOnly
:只能在網路傳輸中使用,當設為 true 時,此 Cookie 就無法在任何 JavaScript 程式碼中取得由於有 domain
值的設定,Cookie 是有指定使用範圍的;僅會在請求的目標網域為 Cookie 設定的 domain 時被帶上;透過 domain
、path
限縮 Cookie 的使用範圍,以及 Max-Age
指定有效期限,建立在 HTTP 這樣無狀態協議上的應用程式,也就得以獲取、控制使用者狀態了。
小提醒一下,有個容易讓人搞混的名詞,叫做「Session Cookies」,指的是沒有指定
Expires
或Max-Age
的 Cookies,當瀏覽器關閉時,這些 Cookies 也會跟著消失,故得名。
由於 Cookie 是有大小數量限制的,單個最大 4K,一個 domain 下最多設置 20 個,不太可能將全部的使用者資料都儲存在瀏覽器端;同時,也因為對目標網域的每個請求都會帶上 Cookies,開發時千萬不要在 Cookie 中存入大量的資料,否則累計下來冗餘的資料傳輸非常可觀。
如果需要儲存更多的資料,或是省下每次傳輸都把 Cookie 帶好帶滿的流量浪費,就需要換成其他的實作方式了。實務上最常見的作法,是在 Cookie 中只存放能代表使用者是誰的 ID,而伺服器端則另外儲存每個 ID 的使用者狀態,當伺服器在收到請求時,藉由 Cookie 中的 ID,對應回使用者的狀態上,這樣就能認出使用者的身分了。
透過這樣簡單的機制,Cookie 的大小限制就不是問題了,但這種從 Cookie 中的 ID 換取狀態的模式,ID 如果被其他人獲取,使用者身分不就被他人使用了嗎?
對,確實有可能會被他人使用。但無論什麼方式,只要是需要知道使用者身分,使用者端自然會需要儲存資料;而只要是需要儲存資料,就都會有被盜取的風險。所以前面提到的 Cookie 屬性中才會有 secure
、HttpOnly
,甚至實驗中的屬性 SameSite
等等,透過正確的設置它們,來降低潛在的風險,盡可能的保護 Cookie 的存取權限,並提高傳輸過程的安全性。
Cookie 及 Session 是網頁應用不可或缺的機制之一,特別是在前端越來越複雜的現代網站上,如何在無狀態的 HTTP 上得知使用者的身分,並接續使用者狀態,是非常重要的一環;但儲存身分的同時,也就代表著身分被冒用的風險,開發者在應用上就需要特別留心注意了。
系列文來到了最後幾篇,內容也預計如同今天的內容,將涵蓋越來越大的範圍;請讀者們跟緊腳步,跟著筆者一起進行最後的收官,完成這趟每日變強之旅吧!
筆者
Gary
半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。相信一切安排都是最好的路。